package net.aiotos.modbus;


import com.alibaba.fastjson.JSON;
import com.intelligt.modbus.jlibmodbus.exception.ModbusIOException;
import com.intelligt.modbus.jlibmodbus.master.ModbusMaster;
import com.intelligt.modbus.jlibmodbus.master.ModbusMasterFactory;
import com.intelligt.modbus.jlibmodbus.serial.*;
import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import modbus.ModbusMasterRTUClientTCP;
import net.aiotos.common.dto.*;
import net.aiotos.driver.IOTOSDriverI;
import net.aiotos.driver.IotosEngine;
import net.aiotos.modbus.entity.ModbusDataParam;
import net.aiotos.modbus.entity.ModbusTCPParam;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


@Slf4j
public class ModbusTCPDriver extends IOTOSDriverI {
    public ModbusTCPDriver(@NonNull DeviceDTO deviceDTO) {
        super(deviceDTO);
    }

    private ModbusMasterRTUClientTCP modbusMasterTCP;

    @Override
    public void InitComm(DeviceDTO attrs) throws Exception {
        setCollectingOneCircle(false);
        // 设置设备上线状态
        ResultDTO resultDTO = getZm().DevOnline(new String[]{getZm().getUuid() + "." + getSysId()});
        if (resultDTO.getCode() != ErrorEnum.Successfull) {
            log.warn("{},deviceId={},上线失败", getClass().getName(), getSysId());
        }
        DeviceConfigDTO deviceConfigDTO = getDeviceConfigDTO(DeviceConfigDTO.class);
        log.info("{}", JSON.toJSONString(deviceConfigDTO));
        ModbusTCPParam modbusTCPParam = deviceConfigDTO.getParam(ModbusTCPParam.class);
        log.info("top:{}:{}", modbusTCPParam.getHost(), modbusTCPParam.getPort());
//        modbusMasterTCP = new ModbusMasterTCP(modbusTCPParam.getHost(), modbusTCPParam.getPort());
        modbusMasterTCP = createRTUClientTcp();
        log.info("modbusMasterTCP:{}", modbusMasterTCP);
    }

    private ModbusMasterRTUClientTCP createRTUServerTcp() throws UnknownHostException, SerialPortException, ModbusIOException, ModbusIOException {
        TcpParameters tcpParameter = new TcpParameters();
        InetAddress host = InetAddress.getByName("127.0.0.1");
        tcpParameter.setHost(host);
        tcpParameter.setPort(503);
        tcpParameter.setKeepAlive(true);

        SerialUtils.setSerialPortFactory(new SerialPortFactoryTcpServer(tcpParameter));

        SerialParameters serialParameter = new SerialParameters();
        // 9600,N,8,1,0
        serialParameter.setDevice("1");
        serialParameter.setBaudRate(SerialPort.BaudRate.BAUD_RATE_9600);
        serialParameter.setParity(SerialPort.Parity.getParity(0));
        serialParameter.setDataBits(8);
        serialParameter.setStopBits(1);
//        serialParameter.set(1);

        SerialUtils.setSerialPortFactory(new SerialPortFactoryTcpClient(tcpParameter));
        ModbusMaster master = ModbusMasterFactory.createModbusMasterRTU(serialParameter);
        master.connect();

        return new ModbusMasterRTUClientTCP(master);
    }

    private ModbusMasterRTUClientTCP createRTUClientTcp() throws UnknownHostException, SerialPortException, ModbusIOException {
        TcpParameters tcpParameter = new TcpParameters();
        InetAddress host = InetAddress.getByName("127.0.0.1");
        tcpParameter.setHost(host);
        tcpParameter.setPort(503);
        tcpParameter.setKeepAlive(true);

        SerialUtils.setSerialPortFactory(new SerialPortFactoryTcpServer(tcpParameter));

        SerialParameters serialParameter = new SerialParameters();
        // 9600,N,8,1,0
        serialParameter.setDevice("1");
        serialParameter.setBaudRate(SerialPort.BaudRate.BAUD_RATE_9600);
        serialParameter.setParity(SerialPort.Parity.getParity(0));
        serialParameter.setDataBits(8);
        serialParameter.setStopBits(1);
//        serialParameter.set(1);

        SerialUtils.setSerialPortFactory(new SerialPortFactoryTcpClient(tcpParameter));
        ModbusMaster master = ModbusMasterFactory.createModbusMasterRTU(serialParameter);
        master.connect();

        return new ModbusMasterRTUClientTCP(master);
    }

    final private Pattern structFormat = Pattern.compile("\\d+\\.?\\d*");

    /**
     * 循环采集方法
     *
     * @param dataDTO
     * @return
     */

    @Override
    public Object[] Collectig(DataDTO dataDTO) {
        Object[] newValues = new Object[]{};
        DataTypeEnum dataTypeEnum = DataTypeEnum.valueOf(dataDTO.getValuetype());
        // 获取数据点配置信息
        DataConfigDTO dataConfigDTO = dataDTO.getConfig().toJavaObject(DataConfigDTO.class);

        List<DataPubDTO> values = new ArrayList<>();
        // 获取用户自定义的数据点参数对象
        if(dataConfigDTO.getParam() == null) {
            return newValues;
        }
        ModbusDataParam dataParam = dataConfigDTO.getParam(ModbusDataParam.class);

        String dataFormat = dataParam.getFormat();

        if (dataFormat == null) {
//            log.error("format is null");
            return newValues;
        }
        Matcher matcher = structFormat.matcher(dataFormat);

        int quantity = 1;
        if (matcher.find()) {
            quantity = Short.valueOf(matcher.group()).intValue();
        }

        final String format = dataFormat.toLowerCase();

        if (format.indexOf('i') != -1) { // I、i类型数据为4个字节，所以n个数据，就是4n字节，除一般应对modbus标准协议的2字节一个数据的个数单位！
            quantity *= 4 / 2;
        } else if (format.indexOf('h') != -1) {
            quantity *= 2 / 2;
        } else if (format.indexOf('b') != -1) {
            quantity *= 1 / 2;
        } else if (format.indexOf('d') != -1) {
            quantity *= 8 / 2;
        } else if (format.indexOf('f') != -1) {
            quantity *= 4 / 2;
        } else if (format.indexOf(
                '?') != -1) {  // 对于功能号1、2的开关量读，开关个数，对于这种bool开关型，个数就不是返回字节数的两倍了！返回的字节个数是动态的，要字节数对应的位数总和，能覆盖传入的个数数值！
            quantity *= 1;
            dataFormat = "";  // 实践发现，对于bool开关型，传入开关量个数就行，format保留为空！如果format设置为 "?"或"8?"、">?"等，都会解析不正确！！
        }

        /**
         * READ_COILS = 1
         * READ_DISCRETE_INPUTS = 2
         * READ_HOLDING_REGISTERS = 3
         * READ_INPUT_REGISTERS = 4
         * WRITE_SINGLE_COIL = 5
         * WRITE_SINGLE_REGISTER = 6
         * READ_EXCEPTION_STATUS = 7
         */
        Object[] objects = new Object[]{};
        Object newValue = null;
        try {
            if (dataParam.getFunId() == 1) {
                objects = modbusMasterTCP.readCoilsToObject(dataParam.getDevId(), dataParam.getRegad(), quantity, dataFormat);
            } else if (dataParam.getFunId() == 2) {
                objects = modbusMasterTCP.readDiscreteInputsToObject(dataParam.getDevId(), dataParam.getRegad(), quantity, dataFormat);
            } else if (dataParam.getFunId() == 3) {
                objects = modbusMasterTCP.readHoldingRegistersToObject(dataParam.getDevId(), dataParam.getRegad(), quantity, dataFormat);
                log.info("f3, {}", JSON.toJSONString(objects));
            } else if (dataParam.getFunId() == 4) {
                objects = modbusMasterTCP.readInputRegistersToObject(dataParam.getDevId(), dataParam.getRegad(), quantity, dataFormat);
            } else if (dataParam.getFunId() == 7) {
                int status = modbusMasterTCP.readExceptionStatus(dataParam.getDevId(), dataParam.getRegad(), quantity);
                objects = new Object[]{status};
            }
            if(objects.length > 0) {
                values = Arrays.asList(dataPub(dataDTO.getDataOid(), objects[0]));
                newValue = objects;
            }
            newValues = objects;
        } catch (Exception e) {
            if (dataParam.getFunId() == 3) {
                log.error("{}", e.getMessage());
                e.printStackTrace();
            }
        }
        log.info("name={}, devId={}, funid={}, regad={}, quantity={}, format={}, value={}", dataDTO.getName(), dataParam.getDevId(), dataParam.getFunId(), dataParam.getRegad(), quantity, dataFormat, JSON.toJSON(newValue));
        return newValues;
    }

    @Override
    public ResultDTO Event_customBroadcast(String fromUuid, String type, String data) {
        return null;
    }

    @Override
    public ResultDTO Event_getData(DataDTO dataDTO, String condition) {
        return ResultDTO.code(500).msg("设备获取接口没有实现");
    }

    @Override
    public ResultDTO Event_setData(DataDTO dataDTO, String value) {
        return ResultDTO.code(500).msg("设备设置接口没有实现");
    }

    @Override
    public ResultDTO Event_syncPubMsg(String point, String value) {
        return null;
    }

    public static void main(String[] args) throws Exception {
        IotosEngine iotosEngine = new IotosEngine(ModbusTCPDriver.class);
        iotosEngine.engineRun(args);
    }
}
